#include "elnotification.h"
#include <QGraphicsDropShadowEffect>
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QTimer>
#include "../basic/elbutton.h"
#include "../basic/elicon.h"

const int ElNotification::Fixed_Width = 300;
const int ElNotification::Icon_Size = 32;
const int ElNotification::Icon_Text_Spacing = 10;
const int ElNotification::Padding_Lr = 15;
const int ElNotification::Padding_Tb = 10;
const int ElNotification::Title_Msg_Spacing = 5;
const int ElNotification::Tb_Spacing = 10;
const int ElNotification::Lr_Spacing = 10;
QList<ElNotification *> ElNotification::notify_managers[4];

ElNotification::ElNotification(QWidget *parent)
    : ElNotification{parent, "", ""}
{}

ElNotification::ElNotification(QWidget *parent,
                               const QString &title,
                               const QString &msg,
                               Type type,
                               int duration,
                               Position position,
                               bool isClosable,
                               int offset)
    : ElComponent{parent}
    , _title(title)
    , _msg(msg)
    , _duration(duration)
    , _position(position)
    , _closable(isClosable)
    , _offset(offset)
{
    setType(type);
    initAnimation();
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);

    // add shadow effect
    auto shadow = new QGraphicsDropShadowEffect(this);
    shadow->setColor(QColor::fromRgba(0x4e000000));
    shadow->setXOffset(0);
    shadow->setYOffset(2);
    shadow->setBlurRadius(25);
    setGraphicsEffect(shadow);

    _fadeTimer = new QTimer(this);
    _fadeTimer->setSingleShot(true);
    _fadeTimer->setTimerType(Qt::CoarseTimer);
    connect(_fadeTimer, &QTimer::timeout, this, [=] {
        _is_repeat_click ^= 1; // avoid click close button when _fadeTimer is about to timeout.
        manager_handle_notify_animation();
    });
    createCloseBtn();

    initPalette();
    hide();

    notify_managers[_position] << this;
    _no = notify_managers[_position].size() - 1;
}

void ElNotification::adjustifyAllPos()
{
    for (auto i = 0; i < 4; ++i) {
        for (auto &notify : notify_managers[i]) {
            auto parent = dynamic_cast<QWidget *>(notify->parent());
            switch (i) {
            case 0: // TopRight
                notify->move(parent->width() - Fixed_Width - Lr_Spacing,
                             Tb_Spacing + notify->_offset
                                 + notify->_no * (notify->height() + Tb_Spacing));
                break;
            case 1: // TopLeft
                notify->move(Lr_Spacing,
                             Tb_Spacing + notify->_offset
                                 + notify->_no * (notify->height() + Tb_Spacing));
                break;
            case 2: // BottomRight
                notify->move(parent->width() - Fixed_Width - Lr_Spacing,
                             parent->height() - notify->_offset
                                 - (notify->_no + 1) * (notify->height() + Tb_Spacing));
                break;
            case 3: // BottomLeft
                notify->move(Lr_Spacing,
                             parent->height() - notify->_offset
                                 - (notify->_no + 1) * (notify->height() + Tb_Spacing));
                break;
            default:
                break;
            }
        }
    }
}

void ElNotification::success(QWidget *parent,
                             const QString &title,
                             const QString &msg,
                             int duration,
                             Position position,
                             bool isClosable,
                             int offset)
{
    auto notify
        = new ElNotification(parent, title, msg, Success, duration, position, isClosable, offset);
    notify->show();
}

void ElNotification::warning(QWidget *parent,
                             const QString &title,
                             const QString &msg,
                             int duration,
                             Position position,
                             bool isClosable,
                             int offset)
{
    auto notify
        = new ElNotification(parent, title, msg, Warning, duration, position, isClosable, offset);
    notify->show();
}

void ElNotification::info(QWidget *parent,
                          const QString &title,
                          const QString &msg,
                          int duration,
                          Position position,
                          bool isClosable,
                          int offset)
{
    auto notify
        = new ElNotification(parent, title, msg, Info, duration, position, isClosable, offset);
    notify->show();
}

void ElNotification::error(QWidget *parent,
                           const QString &title,
                           const QString &msg,
                           int duration,
                           Position position,
                           bool isClosable,
                           int offset)
{
    auto notify
        = new ElNotification(parent, title, msg, Error, duration, position, isClosable, offset);
    notify->show();
}

QString ElNotification::title() const
{
    return _title;
}

void ElNotification::setTitle(const QString &title)
{
    if (_title == title)
        return;
    _title = title;
}

QString ElNotification::message() const
{
    return _msg;
}

void ElNotification::setMessage(const QString &msg)
{
    if (_msg == msg)
        return;
    _msg = msg;
}

ElNotification::Type ElNotification::type() const
{
    switch (_theme.type()) {
    case ElTheme::Success:
        return Success;
    case ElTheme::Warning:
        return Warning;
    case ElTheme::Information:
        return Info;
    case ElTheme::Danger:
        return Error;
    default:
        return Default;
    }
}

void ElNotification::setType(Type type)
{
    ElTheme::Type themeType = ElTheme::Default;
    delete _icon;
    switch (type) {
    case Success:
        themeType = ElTheme::Success;
        _icon = new ElIcon(":/icons/success.svg", this);
        _icon->fillBackground(ThemeColor::successColor);
        _icon->resize(Icon_Size, Icon_Size);
        break;
    case Warning:
        themeType = ElTheme::Warning;
        _icon = new ElIcon(":/icons/warning.svg", this);
        _icon->fillBackground(ThemeColor::warningColor);
        _icon->resize(Icon_Size, Icon_Size);
        break;
    case Info:
        themeType = ElTheme::Information;
        _icon = new ElIcon(":/icons/info.svg", this);
        _icon->fillBackground(ThemeColor::infoColor);
        _icon->resize(Icon_Size, Icon_Size);
        break;
    case Error:
        themeType = ElTheme::Danger;
        _icon = new ElIcon(":/icons/error.svg", this);
        _icon->fillBackground(ThemeColor::dangerColor);
        _icon->resize(Icon_Size, Icon_Size);
        break;
    default:
        _icon = nullptr;
        break;
    }
    _theme.setType(themeType);
}

ElIcon *ElNotification::icon() const
{
    return _icon;
}

void ElNotification::setIcon(ElIcon *icon)
{
    // if you want to set with origin pointer of icon, just use function icon()'s returned value
    if (type() != Default || _icon == icon)
        return;
    delete _icon;
    _icon = icon;
    _icon->resize(Icon_Size, Icon_Size); // other details of icon are user's duty
}

void ElNotification::setIcon(const QString &fileName)
{
    if (type() != Default) // type being set would cause setIcon() losing efficacy
        return;
    delete _icon;
    _icon = new ElIcon(fileName, this);
    _icon->resize(Icon_Size, Icon_Size);
    _icon->fillBackground(ThemeColor::textPrimaryColor);
}

int ElNotification::duration() const
{
    return _duration;
}

void ElNotification::setDuration(int duration)
{
    if (_duration == duration)
        return;
    _duration = duration;
}

ElNotification::Position ElNotification::position() const
{
    return _position;
}

void ElNotification::setPosition(Position position)
{
    if (_position == position)
        return;
    // remove from original notify_managers
    notify_managers[_position].remove(_no);
    _position = position;
    // append into current notify_managers
    notify_managers[_position] << this;
    _no = notify_managers[_position].size() - 1;
    update();
}

bool ElNotification::isClosable() const
{
    return _closable;
}

void ElNotification::setClosable(bool closable)
{
    if (_closable == closable)
        return;
    _closable = closable;
    if (_closable) {
        createCloseBtn();
    } else {
        delete _closeBtn;
        _closeBtn = nullptr;
    }
    update();
}

int ElNotification::offset() const
{
    return _offset;
}

void ElNotification::setOffset(int offset)
{
    if (_offset == offset)
        return;
    _offset = offset;
    update();
}

void ElNotification::fadeIn()
{
    // if you want to understand code, please go to see elmessage.cpp, which is very similar
    // and has annotations.
    move(-width(), -height()); // avoid appear when repaint.
    repaint();                 // repaint to update size otherwise it would cause a confusing layout
    auto parent = dynamic_cast<QWidget *>(this->parent());
    // animation of x and y coordinates must be distinguished
    // otherwise using pos animation will cause conflicts
    auto xAnim = new QPropertyAnimation(this, "x", this);
    xAnim->setDuration(300);
    xAnim->setEasingCurve(QEasingCurve::OutQuad);
    int y = 0;
    if (_position == TopRight || _position == TopLeft) {
        y = Tb_Spacing + _no * (Tb_Spacing + height()) + _offset;
    } else if (_position == BottomRight || _position == BottomLeft) {
        y = parent->height() - (_no + 1) * (Tb_Spacing + height()) - _offset;
    }
    setY(y);
    switch (_position) {
    case TopRight:
        xAnim->setStartValue(parent->width());
        xAnim->setEndValue(parent->width() - Fixed_Width - Lr_Spacing);
        break;
    case TopLeft:
        xAnim->setStartValue(-width());
        xAnim->setEndValue(Lr_Spacing);
        break;
    case BottomRight:
        xAnim->setStartValue(parent->width());
        xAnim->setEndValue(parent->width() - Fixed_Width - Lr_Spacing);
        break;
    case BottomLeft:
        xAnim->setStartValue(-width());
        xAnim->setEndValue(Lr_Spacing);
    default:
        break;
    }
    xAnim->start(QAbstractAnimation::DeleteWhenStopped);
    _animOpa->setDirection(QAbstractAnimation::Forward);
    _animOpa->start();
    startIconAnimation();
}

void ElNotification::fadeOut()
{
    startIconAnimation(false); // icon opacity animation
    _animOpa->setDirection(QAbstractAnimation::Backward);
    connect(_animOpa, &QPropertyAnimation::finished, this, &ElNotification::deleteLater);
    _animOpa->start();
}

void ElNotification::manager_handle_notify_animation()
{
    fadeOut();
    notify_managers[_position].remove(_no); // remove this from notify_managers[_position]
    for (qsizetype i = _no; i < notify_managers[_position].size(); ++i) {
        /* be careful that notify->_no isn't equal to i or this->_no.
         * Their relationship is `notify->_no - 1 == this->_no`.
         * And since `this` was removed from notify_managers[_position],
         * there exists notify->_no >= 1, or notify->_no != 0
         */
        auto &notify = notify_managers[_position][i];
        qsizetype start_y, end_y;
        switch (_position) {
        case TopRight:
        case TopLeft:
            start_y = Tb_Spacing + notify->_offset + notify->_no * (Tb_Spacing + notify->height());
            end_y = -notify->height() + notify->_offset
                    + notify->_no * (Tb_Spacing + notify->height());
            break;
        case BottomRight:
        case BottomLeft: {
            auto parent = dynamic_cast<QWidget *>(notify->parent());
            start_y = parent->height() - notify->_offset
                      - (notify->_no + 1) * (Tb_Spacing + notify->height());
            end_y = parent->height() - notify->_offset
                    - notify->_no * (Tb_Spacing + notify->height());
        } break;
        default:
            break;
        }
        notify->_animY->setStartValue(start_y);
        notify->_animY->setEndValue(end_y);
        notify->_animY->start();
        notify->_no = i;
    }
}

void ElNotification::paintEvent(QPaintEvent *e)
{
    QPainter painter(this);
    // get bounding rect
    auto font = painter.font();
    font.setPointSize(10);
    font.setBold(true);
    painter.setFont(font);
    QRectF title_br, msg_br;
    painter.drawText(QRectF(), Qt::AlignCenter, _title, &title_br);
    painter.drawText(QRectF(), Qt::AlignCenter, _msg, &msg_br);
    resize(Fixed_Width, 2 * Padding_Tb + title_br.height() + Title_Msg_Spacing + msg_br.height());
    // draw rect
    painter.setBrush(Qt::white);
    painter.setPen(_palette.color(ElPalette::Border));
    painter.drawRoundedRect(getDrawRect(), height() / 8.0, height() / 8.0);
    // draw message and title
    painter.setPen(ThemeColor::textPrimaryColor);
    bool hasIcon = (type() != Default) || (_icon != nullptr);
    auto left = Padding_Lr + (hasIcon ? Icon_Size + Icon_Text_Spacing : 0);
    QRectF title_drawing_rect(left, Padding_Tb, title_br.width(), title_br.height());
    painter.drawText(title_drawing_rect, Qt::AlignVCenter | Qt::AlignLeft, _title);
    font.setPointSize(9);
    font.setBold(false);
    painter.setFont(font);
    auto top = Padding_Tb + title_br.height() + Title_Msg_Spacing;
    QRectF msg_drawing_rect(left, top, msg_br.width(), msg_br.height());
    painter.drawText(msg_drawing_rect, Qt::AlignVCenter | Qt::AlignLeft, _msg);
    // draw icon
    if (hasIcon) {
        _icon->move(Padding_Lr, (height() - Icon_Size) / 2);
        _icon->show();
    }
    // draw close button
    if (_closable) {
        _closeBtn->move(width() - _closeBtn->width() - 8, Padding_Tb - 5);
        _closeBtn->setOpacity(_opacity);
        _closeBtn->show();
    }
    e->accept();
}

void ElNotification::showEvent(QShowEvent *e)
{
    fadeIn();
    if (_duration > 0)
        _fadeTimer->start(_duration);
    e->accept();
}

void ElNotification::enterEvent(QEnterEvent *e)
{
    _fadeTimer->stop();
    e->accept();
}

void ElNotification::leaveEvent(QEvent *e)
{
    if (_duration > 0) {
        if (_fadeTimer->isActive())
            _fadeTimer->stop();
        _fadeTimer->start();
    }
    e->accept();
}

void ElNotification::mouseReleaseEvent(QMouseEvent *e)
{
    auto pos = e->pos();
    if (pos.x() >= 0 && pos.x() <= width() && pos.y() >= 0 && pos.y() <= height()) {
        emit clicked();
    }
    e->accept();
}

void ElNotification::initPalette()
{
    _palette.setColor(ElPalette::Border, QColor(235, 238, 245));
}

void ElNotification::initAnimation()
{
    _animY = new QPropertyAnimation(this, "y", this);
    _animY->setDuration(300);
    _animY->setEasingCurve(QEasingCurve::OutQuad);
    _animOpa = new QPropertyAnimation(this, "opacity", this);
    _animOpa->setDuration(300);
    _animOpa->setEasingCurve(QEasingCurve::OutQuad);
    _animOpa->setStartValue(0);
    _animOpa->setEndValue(1);
    _iconAnimOpa = new QPropertyAnimation(this);
    _iconAnimOpa->setPropertyName("background");
    _iconAnimOpa->setDuration(300);
    _iconAnimOpa->setEasingCurve(QEasingCurve::OutQuad);
}

void ElNotification::startIconAnimation(bool isForward)
{
    if (_icon == nullptr)
        return;
    _iconAnimOpa->setTargetObject(_icon);
    QColor bgColor = ThemeColor::textPrimaryColor;
    switch (type()) {
    case Success:
        bgColor = ThemeColor::successColor;
        break;
    case Warning:
        bgColor = ThemeColor::warningColor;
        break;
    case Info:
        bgColor = ThemeColor::infoColor;
        break;
    case Error:
        bgColor = ThemeColor::dangerColor;
        break;
    default:
        break;
    }
    bgColor.setAlphaF(0);
    _iconAnimOpa->setStartValue(bgColor);
    bgColor.setAlphaF(1);
    _iconAnimOpa->setEndValue(bgColor);
    _iconAnimOpa->setDirection(isForward ? QAbstractAnimation::Forward
                                         : QAbstractAnimation::Backward);
    _iconAnimOpa->start();
}

void ElNotification::createCloseBtn()
{
    _closeBtn = new ElTextButton("×", this);
    _closeBtn->setClipped(true);
    auto font = _closeBtn->font();
    font.setPointSize(12);
    _closeBtn->setFont(font);
    auto pal = _closeBtn->palette();
    pal.setColor(ElPalette::Normal, ElPalette::Foreground, ThemeColor::textDisabledColor);
    pal.setColor(ElPalette::Entered, ElPalette::Foreground, ThemeColor::infoColor);
    pal.setColor(ElPalette::Pressed, ElPalette::Foreground, ThemeColor::textPrimaryColor);
    _closeBtn->setPalette(pal);
    _closeBtn->hide();
    connect(_closeBtn, &ElTextButton::clicked, this, [=] {
        if (_is_repeat_click) // avoid click repeatly. App will crash without it.
            return;
        _is_repeat_click ^= 1;
        _fadeTimer->stop();
        manager_handle_notify_animation(); // called manually
    });
}
